﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net.NetworkInformation;
using System.Threading;
using System.Windows.Forms;
using DevelopersToolbox;

namespace NetworkTools {
    public partial class PingControl : UserControl, IExecutableTool {
        private readonly Ping _ping = new Ping();
        private Thread _thread;
        private bool _stop;
        private readonly EventWaitHandle _stopWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
        private string _hostName;
        private DateTime _startTime;
        private const double _timeInMsToDisplayInGraph = 60000;

        public PingControl() {
            InitializeComponent();
            _ping.PingCompleted += _ping_PingCompleted;
            _resultsGraph.DataSets = new[] {
               new DataSet {
                   Color = Color.Blue,
                   DataPoints = new DataPoint[0]
               }
            };
        }

        void _ping_PingCompleted(object sender, PingCompletedEventArgs e) {
            try {
                this.BeginInvoke(new MethodInvoker(() => PingResultsReceived(e)));
            } catch (Exception) {
                // TODO: This is called when we exit but haven't stopped pinging. There is probably a better way to handle this.
                Stop();
            }
        }

        private void PingResultsReceived(PingCompletedEventArgs e) {
            if (e.Error != null) {
                _results.Text += string.Format("{0}\n", e.Error.Message);
                _results.Select(_results.TextLength, 0);
                _results.ScrollToCaret();
            }
            if (e.Reply != null) {
                _results.Text += string.Format("{0}\n", ReplyToString(e.Reply));
                _results.Select(_results.TextLength, 0);
                _results.ScrollToCaret();

                int msFromStartTime = (int)(DateTime.Now - _startTime).TotalMilliseconds;
                List<DataPoint> dataPoints = new List<DataPoint>(_resultsGraph.DataSets[0].DataPoints);
                DataPoint dp = new DataPoint {
                    X = msFromStartTime,
                    Y = e.Reply.RoundtripTime
                };
                dataPoints.Add(dp);

                // calculate min/max X
                _resultsGraph.MaxX = dataPoints[dataPoints.Count - 1].X;
                _resultsGraph.MinX = Math.Max(0, _resultsGraph.MaxX - _timeInMsToDisplayInGraph);
                _resultsGraph.MaxX = _resultsGraph.MinX + _timeInMsToDisplayInGraph;
                while (dataPoints.Count > 0 && dataPoints[0].X < _resultsGraph.MinX) {
                    dataPoints.RemoveAt(0);
                }

                // calculate min/max Y
                _resultsGraph.MinY = 0;
                _resultsGraph.MaxY = double.MinValue;
                foreach (DataPoint dataPoint in dataPoints) {
                    if (dataPoint.Y > _resultsGraph.MaxY) {
                        _resultsGraph.MaxY = dataPoint.Y + 1;
                    }
                }

                _resultsGraph.DataSets[0].DataPoints = dataPoints.ToArray();
                _resultsGraph.Refresh();
            }
        }

        private string ReplyToString(PingReply reply) {
            if (reply.Status == IPStatus.Success) {
                return string.Format("Reply from {0}: bytes={1} time={2}ms TTL={3}", reply.Address, reply.Buffer.Length, reply.RoundtripTime, reply.Options.Ttl);
            } else {
                return string.Format("{0}", reply.Status);
            }
        }

        private void _ping_Click(object sender, EventArgs e) {
            if (_thread != null) {
                Stop();
            } else {
                Execute();
            }
        }

        private void Stop() {
            _stop = true;
            _stopWaitHandle.Set();
            _thread.Join();
            _thread = null;
            _pingButton.Text = "Ping";
        }

        public void Execute() {
            _pingButton.Text = "Stop";
            if (_thread != null) {
                Stop();
                _thread.Join();
            }
            _results.Text = "";
            _startTime = DateTime.Now;
            _hostName = _address.Text;
            _stop = false;
            _stopWaitHandle.Reset();
            _thread = new Thread(PingThread);
            _thread.Start();
        }

        private void PingThread() {
            while (!_stop) {
                _stopWaitHandle.WaitOne(1000, false);
                if (_stop) {
                    break;
                }
                try {
                    _ping.SendAsync(_hostName, null);
                } catch (Exception) {
                    // TODO handle errors.
                }
            }
        }

        private void _address_KeyPress(object sender, KeyPressEventArgs e) {
            if (e.KeyChar == '\r') {
                Execute();
            }
        }
    }
}
